frontend/pages/e/[uuid]/details.tsx (view raw)
1import moment from 'moment';
2import Linkify from 'linkify-react';
3import Tooltip from '@mui/material/Tooltip';
4import IconButton from '@mui/material/IconButton';
5import Box from '@mui/material/Box';
6import Link from '@mui/material/Link';
7import Card from '@mui/material/Card';
8import Container from '@mui/material/Container';
9import TextField from '@mui/material/TextField';
10import Typography from '@mui/material/Typography';
11import TuneIcon from '@mui/icons-material/Tune';
12import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
13import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
14import {useTheme} from '@mui/material/styles';
15import {DatePicker} from '@mui/x-date-pickers/DatePicker';
16import {PropsWithChildren, useState} from 'react';
17import {useTranslation} from 'next-i18next';
18import pageUtils from '../../../lib/pageUtils';
19import DetailsLink from '../../../containers/DetailsLink';
20import ShareEvent from '../../../containers/ShareEvent';
21import PlaceInput from '../../../containers/PlaceInput';
22import LangSelector from '../../../components/LangSelector';
23import usePermissions from '../../../hooks/usePermissions';
24import useEventStore from '../../../stores/useEventStore';
25import useToastStore from '../../../stores/useToastStore';
26import EventLayout, {TabComponent} from '../../../layouts/Event';
27import {
28 EventByUuidDocument,
29 useUpdateEventMutation,
30} from '../../../generated/graphql';
31
32interface Props {
33 eventUUID: string;
34 announcement?: string;
35}
36
37const Page = (props: PropsWithChildren<Props>) => {
38 return <EventLayout {...props} Tab={DetailsTab} />;
39};
40
41const DetailsTab: TabComponent<Props> = ({}) => {
42 const {t} = useTranslation();
43 const {
44 userPermissions: {canEditEventDetails},
45 } = usePermissions();
46 const theme = useTheme();
47 const [updateEvent] = useUpdateEventMutation();
48 const addToast = useToastStore(s => s.addToast);
49 const setEventUpdate = useEventStore(s => s.setEventUpdate);
50 const event = useEventStore(s => s.event);
51 const [isEditing, setIsEditing] = useState(false);
52
53 if (!event) return null;
54
55 const hasGeoloc = event.latitude && event.longitude;
56
57 const onSave = async e => {
58 try {
59 const {uuid, ...data} = event;
60 const {
61 id,
62 travels,
63 waitingPassengers,
64 __typename,
65 administrators,
66 passengers,
67 ...input
68 } = data;
69 await updateEvent({
70 variables: {
71 uuid,
72 eventUpdate: {
73 ...input,
74 },
75 },
76 refetchQueries: ['eventByUUID'],
77 });
78 setIsEditing(false);
79 } catch (error) {
80 console.error(error);
81 addToast(t('event.errors.cant_update'));
82 }
83 };
84
85 const modifyButton = isEditing ? (
86 <Tooltip
87 title={t('event.details.save')}
88 sx={{
89 position: 'absolute',
90 top: theme.spacing(2),
91 right: theme.spacing(2),
92 }}
93 >
94 <IconButton color="primary" onClick={onSave}>
95 <CheckCircleOutlineIcon />
96 </IconButton>
97 </Tooltip>
98 ) : (
99 <Tooltip
100 title={t('event.details.modify')}
101 sx={{
102 position: 'absolute',
103 top: theme.spacing(2),
104 right: theme.spacing(2),
105 }}
106 >
107 <IconButton color="primary" onClick={() => setIsEditing(true)}>
108 <TuneIcon />
109 </IconButton>
110 </Tooltip>
111 );
112
113 return (
114 <Box
115 sx={{
116 position: 'relative',
117 }}
118 >
119 <Container
120 sx={{
121 p: 4,
122 mt: 6,
123 mb: 11,
124 mx: 0,
125 [theme.breakpoints.down('md')]: {
126 p: 2,
127 },
128 }}
129 >
130 <Card
131 sx={{
132 position: 'relative',
133 maxWidth: '100%',
134 width: '480px',
135 p: 2,
136 }}
137 >
138 <Typography variant="h4" pb={2}>
139 {t('event.details')}
140 </Typography>
141 {canEditEventDetails() && modifyButton}
142 {(isEditing || event.name) && (
143 <Box pt={2} pr={1.5}>
144 <Typography variant="overline">
145 {t('event.fields.name')}
146 </Typography>
147 <Typography>
148 {isEditing ? (
149 <TextField
150 size="small"
151 fullWidth
152 value={event.name}
153 onChange={e => setEventUpdate({name: e.target.value})}
154 name="name"
155 id="EditEventName"
156 />
157 ) : (
158 <Typography id="EventName">{event.name}</Typography>
159 )}
160 </Typography>
161 </Box>
162 )}
163 {(isEditing || event.date) && (
164 <Box pt={2} pr={1.5}>
165 <Typography variant="overline">
166 {t('event.fields.date')}
167 </Typography>
168 {isEditing ? (
169 <Typography>
170 <DatePicker
171 slotProps={{
172 textField: {
173 size: 'small',
174 id: `EditEventDate`,
175 fullWidth: true,
176 placeholder: t('event.fields.date_placeholder'),
177 },
178 }}
179 format="DD/MM/YYYY"
180 value={moment(event.date)}
181 onChange={date =>
182 setEventUpdate({
183 date: !date ? null : moment(date).format('YYYY-MM-DD'),
184 })
185 }
186 />
187 </Typography>
188 ) : (
189 <Box position="relative">
190 <Typography id="EventDate">
191 {moment(event.date).format('DD/MM/YYYY')}
192 </Typography>
193 </Box>
194 )}
195 </Box>
196 )}
197 {(isEditing || event.address) && (
198 <Box pt={2} pr={1.5}>
199 <Typography variant="overline">
200 {t('event.fields.address')}
201 </Typography>
202 {isEditing ? (
203 <PlaceInput
204 place={event.address}
205 latitude={event.latitude}
206 longitude={event.longitude}
207 onSelect={({place, latitude, longitude}) =>
208 setEventUpdate({
209 address: place,
210 latitude,
211 longitude,
212 })
213 }
214 />
215 ) : (
216 <Box position="relative">
217 <Typography
218 id="EventAddress"
219 title={t`placeInput.noCoordinates`}
220 sx={{
221 pr: 3,
222 display: 'inline-flex',
223 alignItems: 'center',
224 columnGap: 1,
225 }}
226 >
227 <Link
228 target="_blank"
229 rel="noreferrer"
230 href={`https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(
231 event.address
232 )}`}
233 onClick={e => e.preventDefault}
234 >
235 {event.address}
236 </Link>
237 {!hasGeoloc && (
238 <InfoOutlinedIcon fontSize="small" color="warning" />
239 )}
240 </Typography>
241 </Box>
242 )}
243 </Box>
244 )}
245 {(isEditing || event.description) && (
246 <Box pt={2} pr={1.5}>
247 <Typography variant="overline">
248 {t('event.fields.description')}
249 </Typography>
250 {isEditing ? (
251 <Typography>
252 <TextField
253 fullWidth
254 multiline
255 maxRows={4}
256 inputProps={{maxLength: 250}}
257 value={event.description || ''}
258 onChange={e =>
259 setEventUpdate({description: e.target.value})
260 }
261 id={`EditEventDescription`}
262 name="description"
263 />
264 </Typography>
265 ) : (
266 <Typography
267 id="EventDescription"
268 sx={{pr: 3, whiteSpace: 'pre-line'}}
269 >
270 <Linkify options={{render: DetailsLink}}>
271 {event.description}
272 </Linkify>
273 </Typography>
274 )}
275 </Box>
276 )}
277 {(isEditing || event.lang) && (
278 <Box pt={2} pr={1.5}>
279 <Typography variant="overline">
280 {t('event.fields.lang')}
281 </Typography>
282 {isEditing ? (
283 <LangSelector
284 value={event.lang}
285 onChange={lang => setEventUpdate({lang})}
286 />
287 ) : (
288 <Typography id="EventLang" sx={{pr: 3}}>
289 {t(`PROTECTED.languages.${event.lang}`)}
290 </Typography>
291 )}
292 </Box>
293 )}
294 {!isEditing && (
295 <ShareEvent
296 title={`Caroster ${event.name}`}
297 sx={{width: '100%', mt: 2}}
298 />
299 )}
300 </Card>
301 </Container>
302 </Box>
303 );
304};
305
306export const getServerSideProps = pageUtils.getServerSideProps(
307 async (context, apolloClient) => {
308 const {uuid} = context.query;
309 const {host = ''} = context.req.headers;
310 let event = null;
311
312 // Fetch event
313 try {
314 const {data} = await apolloClient.query({
315 query: EventByUuidDocument,
316 variables: {uuid},
317 });
318 event = data?.eventByUUID?.data;
319 } catch (error) {
320 return {
321 notFound: true,
322 };
323 }
324
325 return {
326 props: {
327 eventUUID: uuid,
328 metas: {
329 title: event?.attributes?.name || '',
330 url: `https://${host}${context.resolvedUrl}`,
331 },
332 },
333 };
334 }
335);
336export default Page;